package com.ljh.myrpc.utils;

import cn.hutool.core.io.resource.ResourceUtil;
import com.ljh.myrpc.serializer.Serializer;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class SpiLoader {

    private static Map<String, Map<String, Class<?>>> loaderMap = new ConcurrentHashMap<>();

    private static Map<String, Object> instanceCache = new ConcurrentHashMap<>();

    private static final String RPC_SYSTEM_SPI_DIR = "META-INF/rpc/system/";

    private static final String RPC_CUSTOM_SPI_DIR = "META-INF/rpc/custom/";

    private static final String[] SCAN_DIRS = new String[]{
            RPC_SYSTEM_SPI_DIR,
            RPC_CUSTOM_SPI_DIR
    };

    private static final List<Class<?>> LOAD_CLASS_LIST = Arrays.asList(Serializer.class);

    public static void loadAll(){
        log.info("加载所有的SPI");
        for (Class<?> aClass : LOAD_CLASS_LIST) {
            load(aClass);
        }
    }

    /**
     * 获取某个接口的实例
     * @param tClass
     * @param key
     * @return
     * @param <T>
     */
    public static <T> T getInstance(Class<?> tClass, String key){
        String tClassName = tClass.getName();
        if(!loaderMap.containsKey(tClassName)){
            synchronized (SpiLoader.class) {
                // 双重检查锁定，防止多线程环境下重复加载SPI
                if(!loaderMap.containsKey(tClassName)){
                    log.info("SPI加载器不包含对应实例，开始加载");
                    load(tClass);
                }
            }
        }
        Map<String, Class<?>> keyClassMap = loaderMap.get(tClassName);
        if(keyClassMap == null){
            throw new RuntimeException(String.format("SPI加载失败，未加载类型为 %s 的SPI", tClassName));
        }
        if(!keyClassMap.containsKey(key)){
            throw new RuntimeException(String.format("SPI加载失败，未找到类型为 %s 的SPI，key为 %s", tClassName, key));
        }
        // 获取到要加载的实现类型
        Class<?> implClass = keyClassMap.get(key);
        // 从实例缓存中加载指定类型的实例
        String implClassName = implClass.getName();
        // Java 的 ConcurrentHashMap 提供了一个线程安全且高效的 computeIfAbsent 方法，可以替代双重检查锁定。
        Object instance = instanceCache.computeIfAbsent(implClassName, k -> {
            try {
                log.info("创建SPI实例，类型为 {}，key为 {}", implClassName, key);
                return implClass.getDeclaredConstructor().newInstance();
            } catch (Exception e) {
                log.error("创建SPI实例失败", e);
                throw new RuntimeException("创建SPI实例失败", e);
            }
        });
        instanceCache.put(implClassName, instance);
//        if(!instanceCache.containsKey(implClassName)){
//            synchronized (implClassName) {
//                // 双重检查锁定，防止多线程环境下重复创建实例
//                if(!instanceCache.containsKey(implClassName)){
//                    try {
//                    log.info("创建SPI实例，类型为 {}，key为 {}", implClassName, key);
//                    // 创建实例
//                    Object instance = implClass.getDeclaredConstructor().newInstance();
//                    instanceCache.put(implClassName, instance);
//                    } catch (Exception e) {
//                        log.error("创建SPI实例失败", e);
//                        throw new RuntimeException("创建SPI实例失败", e);
//                    }
//                }
//            }
//        }
        log.info("获取SPI实例，类型为 {}，key为 {}", implClassName, key);
        return (T) instanceCache.get(implClassName);

    }
    /**
     * 加载某个类型
     * @param loadClass
     * @return
     */
    public static Map<String, Class<?>> load(Class<?> loadClass) {
        log.info("加载类型为 {} 的SPI", loadClass.getName());
        Map<String, Class<?>> keyClassMap = new HashMap<>();
        for (String scanDir : SCAN_DIRS) {
            List<URL> resources = ResourceUtil.getResources(scanDir + loadClass.getName());
            // 读取每个资源文件
            for (URL resource : resources) {
                try(InputStreamReader inputStreamReader = new InputStreamReader(resource.openStream());
                    BufferedReader bufferedReader = new BufferedReader(inputStreamReader)){
                    String line;
                    while((line = bufferedReader.readLine()) != null){
                        String[] strArray = line.split("=");
                        if(strArray.length > 1){
                            String key = strArray[0];
                            String className = strArray[1];
                            keyClassMap.put(key, Class.forName(className));
                        }
                    }
                }catch (Exception e) {
                    log.error("加载SPI失败", e);
                }
            }
        }
        loaderMap.put(loadClass.getName(), keyClassMap);
        return keyClassMap;
    }
}
